package de.zalando.toga.generator.dimensions;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
public class ObjectDimension extends Dimension {
private static class Prefix {
final JsonNode value;
final Prefix parent;
final String key;
Prefix(Prefix parent, String key, JsonNode value) {
this.parent = parent;
this.value = value;
this.key = key;
}
// put the whole prefix into given collection
ObjectNode addTo(ObjectNode node) {
if (parent != null) {
parent.addTo(node);
}
node.put(key, value);
return node;
}
}
private static class PrettyPrinter extends DefaultPrettyPrinter {
public static final PrettyPrinter instance = new PrettyPrinter();
public PrettyPrinter() {
_arrayIndenter = DefaultPrettyPrinter.Lf2SpacesIndenter.instance;
}
}
private final List<Dimension> attributes = new ArrayList<>();
public ObjectDimension(String name, JsonNode node) {
super(name);
Iterator<Map.Entry<String, JsonNode>> iterator = node.fields();
while (iterator.hasNext()) {
Map.Entry<String, JsonNode> entry = iterator.next();
attributes.add(Dimension.from(entry.getKey(), entry.getValue()));
}
}
@Override
public List<JsonNode> getValueDimensions() {
ObjectMapper mapper = new ObjectMapper();
// empty object special case
if (attributes.isEmpty()){
return singletonList(mapper.createObjectNode());
}
// get value dimensions for each attribute
Map<String, List<JsonNode>> dimensions = attributes.stream().collect(toMap(Dimension::getFieldName, Dimension::getValueDimensions));
// permutate
//http://stackoverflow.com/questions/32131987/how-can-i-make-cartesian-product-with-java-8-streams
return permute(dimensions).collect(toList());
}
/**
* Generate a json representation of this object.
* @return a String containing a json array with all possible permutations of the given object.
*/
public String generateJson() {
JsonFactory factory = new JsonFactory();
StringWriter stringWriter = new StringWriter();
ObjectMapper objectMapper = new ObjectMapper();
try {
JsonGenerator generator = factory.createGenerator(stringWriter);
ArrayNode arrayNode = objectMapper.createArrayNode();
getValueDimensions().stream().forEach(arrayNode::add);
objectMapper.writeTree(generator, arrayNode);
return toPrettyString(stringWriter.toString());
} catch (IOException e) {
throw new RuntimeException("Error writing node json nodes.", e);
}
}
private String toPrettyString(String json) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
Object temp = objectMapper.readValue(json, Object.class);
return objectMapper.writer(PrettyPrinter.instance).writeValueAsString(temp);
}
private Stream<JsonNode> combine(
Map<String, List<JsonNode>> map, List<String> keys, int offset, Prefix prefix) {
String key = keys.get(offset);
if (offset == map.size() - 1)
return map.get(key).stream()
.map(node -> new Prefix(prefix, key, node).addTo(new ObjectMapper().createObjectNode()));
return map.get(key).stream()
.flatMap(node -> combine(map, keys, offset + 1, new Prefix(prefix, key, node)));
}
private Stream<JsonNode> permute(
Map<String, List<JsonNode>> map) {
if (map.isEmpty())
return Stream.empty();
return combine(map, new ArrayList<>(map.keySet()), 0, null);
}
}